Skip to content

Schedule one daily optimization time, because a volume holds one - #96

Merged
ALERTua merged 1 commit into
mainfrom
bugfix/94
Aug 25, 2026
Merged

Schedule one daily optimization time, because a volume holds one#96
ALERTua merged 1 commit into
mainfrom
bugfix/94

Conversation

@ALERTua

@ALERTua ALERTua commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Closes #94.

The defect

The script asked for up to four daily optimization start times, then called Set-ReFSDedupSchedule once per time in a loop. That cmdlet replaces a volume's schedule rather than adding to it, so only the last time survived — while the plan the user approved, the progress lines during the run and the closing reminder all promised every time.

the run printed        Scheduling the daily job at 11:00 (2h)
                       Scheduling the daily job at 17:00 (2h)
the volume answers     Start : 17:00        (Get-ReFSDedupSchedule -Volume E:)

Resolve-DedupReadBackVerdict did not catch it: it compared mode, format and level, never the time.

Why one time, established three ways

  1. Documented. Set-ReFSDedupSchedule declares -Start <DateTime> — mandatory, singular, not an array. The -Volume <String> description is the one that says "Enter one or more volume IDs … Separate multiple volumes with a comma"; that phrase is about volumes, and reading it as being about schedules is where the multi-schedule accommodation in this codebase came from.
  2. Refused. Passing an array of two times fails with "Cannot convert 'System.Object[]' to the type 'System.DateTime' required by parameter 'Start'".
  3. Measured. On a virtual disk created and formatted as a Dev Drive for the purpose, three consecutive calls with 11:00, 15:00 and 19:00 each left exactly one schedule and one trigger, holding that call's own time. The same probe established that the scheduled tasks are created by Enable-ReFSDedup rather than by Set-ReFSDedupSchedule, and that Get-ReFSDedupSchedule returns Start as a System.DateTime carrying the date the schedule was written.

The same loop is present unchanged as far back as a794ebb, so this is not a regression — it never worked.

What changed

  • The question asks for one daily start time, still as HH:MM so 18:30 remains possible.
  • Resolve-DedupTimeListInput and Format-DedupTimeList are gone. The daily question now uses Resolve-DedupTimeInput, which the weekly start-time question already used; with one time, Format-DedupTimeList had become a function that returned its own argument.
  • The schedule is written with a single call. A syntax-tree assertion pins that: Set-ReFSDedupSchedule appears exactly once, and no ancestor of that call is a loop statement or a script block — so neither foreach nor ForEach-Object can bring the defect back.
  • The read-back compares the start time, which is what would have caught this. The DateTime the cmdlet returns is rendered invariantly by Format-DedupScheduleStart; a value that is not a date and time answers empty, and the verdict names that gap rather than passing over it.
  • The end-of-run reminder tells the user Task Scheduler will accept further triggers by hand, that nothing here reports them back, that the next schedule written for the drive removes them, and that whether the optimization actually runs on such a trigger has not been confirmed. That last clause is deliberate: a hand-added trigger was measured to stick, but no run on one was ever observed.
  • The arguments keep the shape that is known to work by hand: -Days a quoted comma-joined string, -Start an HH:MM string, -CpuPercentage a plain number. Building -Start as a real DateTime was considered and dropped — string-to-DateTime conversion gives the identical correct result under en-US, uk-UA, fi-FI, de-DE, th-TH and ar-SA.
  • Get-DedupVolumeReport's docstring and one test name stopped resting on the "one or more" misreading.

Checks

Parse OK; Invoke-ScriptAnalyzer -Path . -Recurse no findings; 665 tests pass (658 before the review round below). The pre-commit hook ran the same three.

The new syntax-tree assertion was proved to have teeth by reintroducing the defect on a scratch copy, in both shapes:

real script        -> PASSES
foreach statement  -> FAILS: the call sits inside a ForEachStatementAst
ForEach-Object     -> FAILS: the call sits inside a ScriptBlockExpressionAst

Review

An independent review raised 17 points; 13 were applied. The three that mattered:

  • A new test mocked the answer 1, so the branch printing the daily-time prompt never ran and its Should -Not -Invoke assertions passed on nothing — and it had replaced a test that did drive that branch. It now answers 2 first and asserts the prompt was printed.
  • The first version of the loop assertion walked only LoopStatementAst, so $times | ForEach-Object { … } would have evaded it while the comment claimed it caught any loop. It now walks the parent chain for both shapes.
  • The comment explaining why the read-back sits before the scrub call had been replaced with a weaker reason, dropping the load-bearing one: the read takes the first schedule it is given, and a scrub entry in that list would answer instead. Restored.

Also applied: $baseScheduleParams renamed to $scheduleParams (nothing is "base" without the clone and the loop); the Scheduled the daily job line removed, since it claimed success from a call having returned and the read-back two lines later reports it properly; the menu no longer offers "these times" where there is one; a test now pins that Resolve-DedupTimeInput and Format-DedupScheduleStart render HH:MM identically, a coupling nothing held before; several comments shortened.

Declined: that an unreadable start time deserves its own header rather than the "does not report back what was asked for" one. The difference line directly beneath already says "the volume reported none that could be read", and splitting the header for a case that arises only if the returned type changes adds structure without adding clarity.

Left open, recorded rather than guessed: whether Get-ReFSDedupSchedule can return a scrub entry beside the daily one — the probe called only Set-ReFSDedupSchedule — and whether Start comes back as a DateTime on every Windows build, which is one measurement on one build.

🤖 Generated with Claude Code

Set-ReFSDedupSchedule replaces a volume's schedule rather than adding to it,
so the loop that called it once per chosen time left only the last one
behind - while the approved plan, the progress lines during the run and the
closing reminder all promised every time.

Established three ways. The documented signature takes -Start <DateTime>,
mandatory and singular. An array of two times is refused outright. And three
consecutive calls on a freshly created Dev Drive each left exactly one
schedule and one trigger, holding that call's own time.

- ask for one daily start time, still as HH:MM so 18:30 stays possible
- drop Resolve-DedupTimeListInput and Format-DedupTimeList; the daily
  question now uses Resolve-DedupTimeInput, as the weekly one already did
- write the schedule with a single call, pinned by a syntax-tree assertion
  that no loop of either shape encloses it
- compare the start time in the read-back, which is what would have caught
  this, rendering the DateTime the cmdlet returns invariantly
- tell the user Task Scheduler accepts further triggers by hand, that
  nothing here reports them back, that the next schedule written removes
  them, and that whether the optimization runs on one is unconfirmed
- stop reading the documentation's "one or more volumes" as "one or more
  schedules"

Closes #94

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kilo-code-bot

kilo-code-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • README.md
  • dev_drive.ps1
  • dev_drive.Tests.ps1

Reviewed by free · Input: 106.8K · Output: 12.2K · Cached: 405.6K

@ALERTua
ALERTua merged commit 0da6563 into main Aug 25, 2026
3 checks passed
@ALERTua
ALERTua deleted the bugfix/94 branch August 25, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Only the last daily optimization time survives, while the plan promises them all

1 participant